var GownFull = Base.extend({
	constructor: function(builder)
	{
		var i;
		var x = this;

		this.im_array = new Array();
		this.type_array = new Array();
		this.im_array[0] = null;
		this.type_array[0] = GownFull.DISABLED;
		this.modifiers = new GFList;
		this.loggers = {};

		this.builder = builder;

		builder.BuildCSS();
		this.mainwin = builder.BuildMainWindow(this);
		this.candwin = builder.BuildCandidateListWindow();
		this.browserwin = builder.BuildBrowserWindow();
		this.internalwin = builder.BuildInternalBufferWindow(this);
		this.menu = builder.BuildMenu(this);
		this.assoc = builder.BuildAssociativeHandler(this);
		this.suggest = builder.BuildSuggestionHandler(this);

		builder.SetHandler(function(obj,key,m) { return x.keydown_handler(obj,key,m); },
				function(obj,key,m) { return x.keyup_handler(obj,key,m); },
				function(obj,key,m) { return x.keypress_handler(obj,key,m); },
				function(obj) { return x.onfocus_handler(obj); },
				function(obj) { return x.lostsel_handler(obj); });

		if(GownFull.instance) {
			Debug.trace(0,'GownFull: Error, more than one instance of GownFull created.');
		}
		GownFull.instance = this;

		builder.PostCreateGownFull();
	},

	mainwin: null,
	candwin: null,
	browserwin: null,
	internalwin: null,
	menu: null,
	assoc: null,
	suggest: null,

	builder: null,

	preedit: '',
	display_preedit: '',
	sel_preedit_adapter: null,

	im_array: null,
	im_current: 0,
	type_array: null,
	download_count: 0,
	modifiers: null,
	loggers: null,
	last_focus_obj: null,
	last_sent_string: null,

	available: true,

	keydown_handler: function(obj,key,modifiers)
	{
		var b,menu_called;

		Debug.trace(4,'GownFull: keydown_handler key=%d, modifiers=%b',key,modifiers);

		for(var k in this.loggers) {
			var logger = this.loggers[k];
			if(logger) logger.keydown_log(key,modifiers);
		}

		b = true;
		if(key == 8) {
			this.assoc.StopAssociativeWord();
			this.suggest.StopSuggestion();
		}

		menu_called = false;
		if(this.menu.available) {
			if(!this.menu.keydown_handler(obj,key,modifiers)) b = false;
			menu_called = true;
		}
		if(this.im_current > 0) {
			var im = this.im_array[this.im_current];
	
			b = im.keydown_handler(obj,key,modifiers);
		}
		if(b && !this.candwin.keydown_handler(obj,key,modifiers)) b = false;
		if(b && (34 <= key && key <= 40)) this.ResetPreeditIfSelect();

		if(!menu_called && !this.menu.keydown_handler(obj,key,modifiers)) b = false;

		Debug.trace(4,'GownFull: keydown_handler finally return %s',b);
		return b;
	},
	keyup_handler: function(obj,key,modifiers)
	{
		var b;

		Debug.trace(4,'GownFull: keyup_handler key=%d, modifiers=%b',key,modifiers);

		for(var k in this.loggers) {
			var logger = this.loggers[k];
			if(logger) logger.keyup_log(key,modifiers);
		}

		b = true;

		if(this.im_current > 0) {
			var im = this.im_array[this.im_current];
	
			b = im.keyup_handler(obj,key,modifiers);
		}
		if(key == 27) b = false; // Force return false for escape key
		Debug.trace(4,'GownFull: keyup_handler finally return %s',b);
		return b;
	},
	keypress_handler: function(obj,key,modifiers)
	{
		var b,menu_called;

		Debug.trace(4,'GownFull: keypress_handler key=%d, modifiers=%b',key,modifiers);

		for(var k in this.loggers) {
			var logger = this.loggers[k];
			if(logger) logger.keypress_log(key,modifiers);
		}

		b = true;
		menu_called = false;
		if(this.menu.available) {
			if(!this.menu.keypress_handler(obj,key,modifiers)) b = false;
			menu_called = true;
		}

		if(b && !this.candwin.keypress_handler(obj,key,modifiers)) b = false;
		if(b && this.im_current > 0) {
			var im = this.im_array[this.im_current];

			b = im.keypress_handler(obj,key,modifiers);
			this.assoc.StopAssociativeWord();
			this.suggest.StopSuggestion();
//			if(b) this.ClearSelectionPreedit();
		}
	
		if(!menu_called && !this.menu.keypress_handler(obj,key,modifiers)) b = false;
		// I want menu cannot be filtered. (no b checking)
		if(key == 27) b = false; // Force return false for escape key
		Debug.trace(4,'GownFull: keypress_handler finally return %s',b);
		return b;
	},
	onfocus_handler: function(obj)
	{
/*		var adapter = GownFullObjectAdapter.GetAdapter(obj);

		if(adapter && this.im_current > 0) {
			var pos;

			pos = adapter.GetCursorXY();
			pos[0] -= GFWindowsMovableHandler.GetScrollX();
			pos[1] -= GFWindowsMovableHandler.GetScrollY();
			this.mainwin.SetWindowPosition(pos[0]+Const.MAIN_OFFSET_X,pos[1]+Const.MAIN_OFFSET_Y);
		}
*/
		this.last_focus_obj = obj;
		if(obj) {
			Debug.trace(2,'GownFull: Set Last Focus Object=%s (%s)',obj,obj.nodeName);
		}
		else {
			Debug.trace(2,'GownFull: Set Last Focus Object=%s',obj);
		}
	},
	lostsel_handler: function(obj)
	{
		this.ResetPreeditIfSelect();
	},
	GetCurrentInputMethod: function()
	{
		if(this.im_current > 0) {
			if(this.type_array[this.im_current] == GownFull.LOCAL) return this.im_array[this.im_current];
			else return null;
		}
		else return null;
	},
	GetCurrentInputMethodIndex: function()
	{
		if(this.im_current > 0) {
			if(this.type_array[this.im_current] == GownFull.LOCAL) return this.im_current;
			else return 0;
		}
		else return 0;
	},
	LookupInputMethodByName: function(name)
	{
		var i,type,im;

		for(i=0;i<this.im_array.length;i++) {
			type = this.type_array[i];
			im = this.im_array[i];

			if(type == GownFull.DISABLED) continue;
			else if(type == GownFull.LOCAL) {
				if(im.IMName() == name) return i;
			}
			else if(type == GownFull.REMOTE) {
				if(im.name == name) return i;
			}
		}
		return -1; // Not found
	},
	GetInputMethodType: function(index)
	{
		var type = this.type_array[index];

		if(!type) return 0;

		return type;
	},
	GetInputMethodName: function(i)
	{
		if(this.type_array[i]) {
			if(this.type_array[i] == GownFull.LOCAL) return this.im_array[i].IMName();
			else if(this.type_array[i] == GownFull.REMOTE) return this.im_array[i].name;
			else if(this.type_array[i] == GownFull.DISABLED) return $GFS('Disabled');
		}
		else return 'Error';
	},
	RegisterInputMethod: function(im)
	{
		var i = this.im_array.length;
		this.im_array[i] = im;
		im.ime = this;
		im.im_id = i;
		this.type_array[i] = GownFull.LOCAL;
		Debug.trace(0,"GownFull: Register a Input Method \"%s\", id=%d",im.IMName(),i+1);
		return (i+1);
	},
	RegisterDownload: function(objname,name,url)
	{
		var i = this.im_array.length;
		var o;

		o = {};
		o.name = name;
		o.objname = objname;
		if(url) o.url = url;
		
		this.im_array[i] = o;
		this.type_array[i] = GownFull.REMOTE;
		Debug.trace(0,"GownFull: Register an available Input Method (objname=\"%s\", name=\"%s\", url=\"%s\" id=%d)",objname,name,url,i+1);
		return (i+1);
	},
	DownloadRemoteInputMethod: function(url,objname,old_i)
	{
		var data = {};
		var nextid;

		Debug.trace(0,"GownFull: Download Remote Input Method %s from %s (id=%d)",objname,url,old_i);
		
		this.SetPreeditText(lib.sprintf("Loading %s...",objname));
		data.objname = objname;
		data.old_i = old_i;
		this.download_count++;

		DownloadManager.NewDownloadName(objname,url,
			function(userdata,serverdata) { 
				if(serverdata) {
					GownFull.instance.DownloadInputMethod2(serverdata,userdata.old_i);
					GownFull.instance.ResetPreedit();
				}
				else {
					GownFull.instance.SetPreeditText(userdata.objname+' does not exist.');
				}
				GownFull.instance.download_count--;
			},
		data,true);

		return true;
	},
	DownloadInputMethod: function(objname,old_i)
	{
		var url;
		var data = {};

		url = lib.sprintf("%s?objname=%s",GownFullConfig.getim_url,objname);
		Debug.trace(0,"GownFull: Download Input Method %s (id=%d)",objname,old_i);
		
		this.SetPreeditText(lib.sprintf("Loading %s...",objname));

		data.objname = objname;
		data.old_i = old_i;
		this.download_count++;
		DownloadManager.NewDownload(url,
			function(userdata,serverdata) { 
				if(serverdata) {
					GownFull.instance.ResetPreedit();
					GownFull.instance.DownloadInputMethod2(serverdata,userdata.old_i);
				}
				else {
					GownFull.instance.SetPreeditText(userdata.objname+' does not exist.');
				}
				GownFull.instance.download_count--;
			},
		data);
		return true;
	},
	DownloadInputMethod2: function(im,old_i)
	{
		this.im_array[old_i] = im;
		im.ime = this;
		im.im_id = old_i;
		this.type_array[old_i] = GownFull.LOCAL;
		this.SelectInputMethod(old_i);
		Debug.trace(0,"GownFull: Input Method \"%s\" downloaded successfully. (id=%d)",im.IMName(),old_i);
		return old_i;
	},
	SelectInputMethod: function(index)
	{
		var im,status,name;

		Debug.trace(2,"GownFull: Selecting input method id=%d",index);

		// disable the current input method first
		if(this.im_current > 0) {
			im = this.im_array[this.im_current];
			im.onDeactive();
		}

		if(index == 0) {
			this.im_current = 0;
			this.ResetPreedit();
			this.candwin.ReleaseAll();
			this.mainwin.UpdateIMName($GFS('Disabled'));
			return true;
		}
		// check the status of the input method first...
		if(!this.type_array[index]) {
			Debug.trace(0,"GownFull: Input Method with id=%d does not exist. Something wrong.",index);
			return false;
		}
		this.im_current = 0;
		this.ResetPreedit();
		this.candwin.ReleaseAll();

		status = this.type_array[index];
		switch(status) {
		case GownFull.DISABLED:
			this.mainwin.UpdateIMName($GFS('Disabled'));
			return true;
			break;
		case GownFull.LOCAL:
			this.im_current = index;
			if(this.im_current > 0) {
				im = this.im_array[this.im_current];
				im.onActive();
				name = im.IMName();
				this.menu.UpdateIMNode(im);
			}
			this.mainwin.UpdateIMName(name);
			return true;
			break;
		case GownFull.REMOTE:
			var obj;

			obj = this.im_array[index];
			if(obj.url) this.DownloadRemoteInputMethod(obj.url,obj.objname,index);
			else this.DownloadInputMethod(obj.objname,index);
			name = obj.name;
			this.mainwin.UpdateIMName(name);
			return true;
		default:
			Debug.trace(0,"GownFull: Input Method with id=%d has invalid status %d",index,status);
			return false;
		}
	},
	ToggleIME: function(show)
	{
		if(show) {
			this.SelectInputMethod(0);
			this.mainwin.ShowWindow(true);
			Debug.trace(1,"GownFull: ToggleIME on");
			this.available = true;
		}
		else {
			this.SelectInputMethod(0);
			this.mainwin.ShowWindow(false);
			this.candwin.ShowWindow(false);
			Debug.trace(1,"GownFull: ToggleIME off");
			this.available = false;
		}
	},
	GetPreedit: function() { return this.preedit; },
	ResetPreedit: function()
	{
		this.mainwin.ResetPreedit();
		this.preedit = '';
		this.display_preedit = '';
		this.ClearSelectionPreedit();
	},
	ResetPreeditIfSelect: function()
	{
		if(this.ClearSelectionPreedit()) {
			this.mainwin.ResetPreedit();
			this.preedit = '';
			this.display_preedit = '';
			var im = this.GetCurrentInputMethod();
			if(im) im.onSelectionModeResetPreedit();
		}
	},
	GetPreeditLength: function()
	{
		return this.preedit.length;
	},
	SetPreeditText: function(str)
	{
		this.mainwin.SetPreeditText(str);
		this.preedit = '';
		this.display_preedit = '';
		this.ClearSelectionPreedit();
	},
	// c - number of characters delete in preedit
	// dc - number of characters delete in display preedit
	DeletePreedit: function(c,dc,o)
	{
		var buf;

		if(dc == null) dc = 1;
		if(c == null) c = 1;

		this.mainwin.DeletePreedit(dc);
		this.preedit = this.preedit.substr(0,this.preedit.length - c);
		this.display_preedit = this.display_preedit.substr(0,this.display_preedit.length - c);
		if(this.display_preedit.length > 0) {
			var im = this.GetCurrentInputMethod();
			if(im && im.IsSelectionPreedit()) {
				if(!o) o = this.last_focus_obj;
				adapter = GownFullObjectAdapter.GetAdapter(o);
				if(adapter) {
					var s = adapter.GetSelectionStart();
					var e = adapter.GetSelectionEnd();
					adapter.InsertAtCursor(this.display_preedit);
					adapter.SetSelection(s,s+this.display_preedit.length);
					this.sel_preedit_adapter = adapter;
				}
			}
		}
		else this.ClearSelectionPreedit();
	},
	AppendPreedit: function(c,dc,o)
	{
		this.mainwin.AppendPreedit(dc);
		this.preedit += c;
		this.display_preedit += dc;
		var im = this.GetCurrentInputMethod();
		if(im && im.IsSelectionPreedit()) {
			if(!o) o = this.last_focus_obj;
			adapter = GownFullObjectAdapter.GetAdapter(o);
			if(adapter) {
				var s = adapter.GetSelectionStart();
				var e = adapter.GetSelectionEnd();
				adapter.InsertAtCursor(this.display_preedit);
				adapter.SetSelection(s,s+this.display_preedit.length);
				this.sel_preedit_adapter = adapter;
			}
		}
	},
	InvalidatePreedit: function()
	{
		this.mainwin.SetPreeditInvalid();
		this.preedit = '';
		this.ClearSelectionPreedit();
		this.display_preedit = '';
	},
	SetPreedit: function(s,ds,o)
	{
		this.mainwin.SetPreedit(ds);
		this.preedit = s;
		this.display_preedit = ds;
		var im = this.GetCurrentInputMethod();
		if(im && im.IsSelectionPreedit()) {
			if(!o) o = this.last_focus_obj;
			adapter = GownFullObjectAdapter.GetAdapter(o);
			if(adapter) {
				var s = adapter.GetSelectionStart();
				var e = adapter.GetSelectionEnd();
				adapter.InsertAtCursor(this.display_preedit);
				adapter.SetSelection(s,s+this.display_preedit.length);
				this.sel_preedit_adapter = adapter;
			}
		}
	},
	ClearSelectionPreedit: function()
	{
		if(this.sel_preedit_adapter) {
			this.sel_preedit_adapter.DeleteSelection();
			this.sel_preedit_adapter = null;
			return true;
		}
		else return false;
	},
	IsInternalBufferAvailable: function()
	{
		return this.internalwin.shown;
	},
	StartInternalBuffer: function(o)
	{
		var old_im;

		old_im = this.GetCurrentInputMethodIndex();

		this.internalwin.SaveInputMethod();

		this.internalwin.DeleteBuffer();

		if(o) this.internalwin.Start(o);
		else this.internalwin.Start(this.last_focus_obj);

		if(old_im > 0) {
			var im = this.im_array[old_im];
			im.onInternalBufferStart(this.internalwin);
		}
	},
	EndInternalBuffer: function()
	{
		var im,b,buffer;

		buffer = this.internalwin.GetBuffer();
		b = false;
		this.internalwin.RestoreInputMethod();

		this.internalwin.PreEnd();
		if(this.im_current > 0) {
			im = this.im_array[this.im_current];
			b = im.onInternalBufferEnd(this.internalwin);
		}
		this.internalwin.End();

		if(!b) {
			if(buffer.length > 0) this.SendString(this.internalwin.target_obj,buffer);
		}
	},
	ForceEndInternalBuffer: function()
	{
		this.internalwin.RestoreInputMethod();
		this.internalwin.PreEnd();
		this.internalwin.End();
	},
	AttachInputLogger: function(logger)
	{
		var name = logger.LoggerName();
		if(this.loggers[name]) {
			Debug.trace(0,"GownFull (AttachInputLogger): Input logger with name %s already exists.",name);
		}
		else {
			logger.ime = this;
			this.loggers[name] = logger;
			logger.onInit();
			Debug.trace(0,"GownFull (AttachInputLogger): Attached input logger %s.",name);
		}
	},
	DetachInputLogger: function(name)
	{
		if(this.loggers[name]) {
			var logger = this.loggers[name];
			logger.onShutdown();
			Debug.trace(0,"GownFull (DetachInputLogger): Detached input logger %s.",name);
		}
		else {
			Debug.trace(1,"GownFull (DetachInputLogger): Input logger with name %s does not exist.",name);
		}
	},
	SendHTML: function(o,s)
	{
		if(!o) {
			o = this.last_focus_obj;
			if(o.focus) o.focus();
		}
		if(!o) {
			Debug.trace(0,"GownFull (SendHTML): No last focus object or target object.");
			return false;
		}

		// don't modifer HTML...now
		var adapter = GownFullObjectAdapter.GetAdapter(o);

		if(adapter) {
			var pos;

			if(adapter.SendHTML(o,s)) {
/*				if(this.im_current > 0) {
					pos = adapter.GetCursorXY();
					pos[0] -= GFWindowsMovableHandler.GetScrollX();
					pos[1] -= GFWindowsMovableHandler.GetScrollY();
					this.mainwin.SetWindowPosition(pos[0]+10,pos[1]);
				}
*/
			}
			else { // This object does not support HTML output, output as string
				adapter.SendString(o,s);
			}
			return true;
		}
		else {
			Debug.trace(0,"GownFull (SendHTML): No Adapter for this object.");
			return false;
		}
		
	},
	SendString: function(o,s,noas)
	{
		var it = this.modifiers.begin();
		var p;

		if(!o) {
			o = this.last_focus_obj;
			if(o.focus) o.focus();
		}
		if(!o) {
			Debug.trace(0,"GownFull (SendString): No last focus object or target object.");
			return false;
		}

		while(!it.nullp()) {
			p = it.current();
			Debug.trace(1,"GownFull: Modify \"%s\" by %s",s,p.ModifierName());
			s = p.Modify(s);
			it.next();
		}
		Debug.trace(1,"GownFull: Send string \"%s\"",s);

		var adapter = GownFullObjectAdapter.GetAdapter(o);

		if(adapter) {
			if(adapter.SendString(o,s)) {
				this.last_sent_string = s;
				var im = this.im_array[this.im_current];
				var pos;
	
				this.suggest.StopSuggestion();
				this.assoc.StopAssociativeWord();

				if(!noas && this.im_current > 0 && im.SuggestionType() == '.*') {
					this.suggest.GenerateSuggestion(s);
					this.assoc.GenerateAssociativeWord(s);
				}

/*				if(this.im_current > 0) {
					pos = adapter.GetCursorXY();
					pos[0] -= GFWindowsMovableHandler.GetScrollX();
					pos[1] -= GFWindowsMovableHandler.GetScrollY();
					this.mainwin.SetWindowPosition(pos[0]+10,pos[1]);
				}
*/
				return true;
			}
			else return false;
		}
		else {
			Debug.trace(0,"GownFull (SendString): No Adapter for this object (nodeName=%s).",o.nodeName);
			return false;
		}
	}
},
{
	instance: null,
	DISABLED: 1,
	LOCAL: 2,
	REMOTE: 3
});
